package com.lx.boot;//说明:

/**
 * 创建人:游林夕/2019/4/28 09 04
 */

import com.lx.annotation.Note;
import com.lx.boot.cache.util.CacheUtils;
import com.lx.boot.config.FirmPropertiesUtil;
import com.lx.boot.db.domain.DataBaseProperties;
import com.lx.boot.web.AuthUtil;
import com.lx.boot.web.config.RequestInfo;
import com.lx.boot.web.filter.ApiEncryptFilter;
import com.lx.entity.Result;
import com.lx.entity.UserInfo;
import com.lx.entity.Var;
import com.lx.util.LX;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.lx.constant.DefaultBaseConstant.*;

@Slf4j
@Configuration
public class OS implements BeanPostProcessor, ApplicationContextAware, EnvironmentAware{
    private static final ThreadLocal<Map<String, Object>> tl = new ThreadLocal() {
        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap(4);
        }
    };

    private static Environment environment;

    private static ApplicationContext applicationContext;

    @Note("获取当前线程ThreadLocal里存储的数据")
    public static  <T>T get(String key){
        return (T) tl.get().get(key);
    }
    @Note("当前线程ThreadLocal进行存储数据")
    public static void put(String key,Object obj){
        tl.get().put(key,obj);
    }
    @Note("删除当前线程ThreadLocal里存储的数据")
    public static void del(String key){
        tl.get().remove(key);
    }
    @Note("清理当前线程ThreadLocal里存储的所有数据")
    public static void remove(){
        tl.remove();
    }

    @Note("设置用户信息")
    public static void setUserInfo(UserInfo userInfo){
        put(USER_INFO, userInfo);
    }
    @Note("获取用户信息")
    public static UserInfo getUserInfo(){
        return LX.ifNull(get(USER_INFO), new UserInfo());
    }
    @Note("获取用户信息")
    public static UserInfo getRealUserInfo(){
        return get(USER_INFO);
    }
    @Note("获取用户信息, T为UserInfo的子类")
    public static <T extends UserInfo>T getUserInfo(Class<T> t){
        return OS.getBean(AuthUtil.class).getUserInfo(t,getRequest());
    }
    @Note("获取用户ID")
    public static String getUserId(){
        return getUserInfo().getUserId();
    }
    @Note("获取用户类型")
    public static String getUserType(){
        return getUserInfo().getUserType();
    }
    @Note("设置自定义数据源链接")
    public static void setCustomDbInfo(DataBaseProperties dataBaseProperties){
        put(CUSTOM_DB_INFO, dataBaseProperties);
    }

    @Note("获取自定义数据源信息")
    public static DataBaseProperties getCustomDbInfo(){
        return get(CUSTOM_DB_INFO);
    }

    @Note("删除自定义数据源信息")
    public static void delCustomDbInfo(){
        del(CUSTOM_DB_INFO);
    }

    @Note("通过name获取 Bean对象")
    public static <T>T getBean(String name){
        return (T)applicationContext.getBean(name);
    }
    @Note("通过name获取 Bean对象")
    public static <T>T getBean(String name,Class<T> t){
        return applicationContext.getBean(name,t);
    }

    @Note("通过Class类型获取 Bean对象")
    public static <T>T getBean(Class<T> t){
        return applicationContext.getBean(t);
    }

    @Note("设置公司ID")
    public static void setFirmId(String firmId){
        put(FIRM_ID, firmId);
    }

    @Note("获取公司ID")
    public static String getFirmId(){
        return get(FIRM_ID);
    }

    @Note("设置第三方接口appid")
    public static void setAppId(String appId){
        put(API_APPID, appId);
    }
    @Note("获取第三方接口appid")
    public static String getAppId(){
        return get(API_APPID);
    }

    @Note("获取配置文件中配置, 优先获取firmid.key 没有获取 key对应的配置信息")
    public static String getProperty(String key){
        return FirmPropertiesUtil.getProperty(key);
    }
    @Note("获取配置文件中配置, 为null时取默认值")
    public static String getProperty(String key, String defaultValue){
        if (environment == null){
            return defaultValue;
        }
        return LX.ifNull(getProperty(key) ,defaultValue);
    }
    @Note("获取配置文件中配置,转成int类型")
    public static int getIntProperty(String key, String defaultValue){
        return Integer.parseInt(getProperty(key, defaultValue));
    }
    @Note("获取配置文件中配置是否是true")
    public static boolean getPropertyIsTrue(String key, String defaultValue){
        return "true".equals(getProperty(key,defaultValue));
    }

    @Note("获取配置文件中配置, 为null时取默认值")
    public static <T>T getProperty(String key, T defaultValue, Class<T> tClass){
        if (environment == null){
            return defaultValue;
        }
        return LX.ifNull(FirmPropertiesUtil.getProperty(key,tClass) ,defaultValue);
    }

    //@ConfigurationProperties 没有动态配置问题时 可以使用这个
    @Note("传入一个实体类型,获取配置信息 实体类需要添加@Properties注解")
    public static <T>T getBeanProperty(Class<T> t){
        return FirmPropertiesUtil.getBeanProperty(t);
    }
    @Note("传入一个实体类型和key前缀,获取配置信息")
    public static <T>T getBeanProperty(Class<T> t, String prefix){
        return FirmPropertiesUtil.getBeanProperty(t,prefix);
    }

    @Note("传入一个实体,更新实体字段 实体类需要添加@Properties注解")
    public static <T>T getBeanProperty(T t){
        return FirmPropertiesUtil.getBeanProperty(t);
    }

    @Note("传入一个实体,和key前缀,更新实体配置信息")
    public static <T>T getBeanProperty(T t, String prefix){
        return FirmPropertiesUtil.getBeanProperty(t, prefix);
    }

    @Note("动态修改系统配置")
    public static void setProperty(Map<String, Object> map){
        addProperty("os.defaultProperty",map);
    }

    @Note("通过程序新增配置 name:配置信息标识, map:配置信息; 注意name名称相同时配置信息会被覆盖")
    public static void addProperty(String name , Map<String, Object> map){
        if (environment == null){
            environment = new StandardEnvironment();
        }
        ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
        MapPropertySource propertySource = new MapPropertySource(name,map);
        configurableEnvironment.getPropertySources().addFirst(propertySource);
    }

    @Note("获取当前服务名")
    public static String getApplicationName(){
        return getProperty(SPRING_APPLICATION_NAME,"springbootApp");
    }

    @Note("获取当前环境, 配置信息中的spring.profiles.active")
    public static String getEnv(){
        return getProperty(SPRING_PROFILES_ACTIVE,"prod");
    }

    @Note("判断当前环境是否为dev环境")
    public static boolean isDev(){
        return "dev".equals(getEnv());
    }
    @Note("判断当前环境是否为prod环境")
    public static boolean isProd(){
        return "prod".equals(getEnv());
    }

    @Note("判断当前环境是否为开发环境的特殊请求")
    public static boolean isDevRequest(){
        return isDev() && "0".equals(getRequest().getHeader(AUTHORIZATION));
    }



    @Note("设置日志追踪ID")
    public static void setLogTraceId(){
        setLogTraceId(LX.uuid32(10));
    }

    @Note("设置日志追踪ID")
    public static void setLogTraceId(String requestId){
        MDC.put("requestId", requestId);
    }

    @Note("获取日志追踪ID")
    public static String getLogTraceId(){
        return MDC.get("requestId");
    }

    @Note("删除日志追踪ID")
    public static void removeTraceId(){
         MDC.remove("requestId");
    }

    @Note("设置指定包下的日志等级")
    public static void setLogLevel(String packageName , LogLevel logLevel){
        getBean(LoggingSystem.class).setLogLevel(packageName, logLevel);
    }



    @Note("设置请求信息")
    public static void setRequestInfo(RequestInfo requestInfo) {
        put(REQUEST_INFO,requestInfo);
    }
    @Note("获取请求信息")
    public static RequestInfo getRequestInfo() {
        return get(REQUEST_INFO);
    }

    @Note("设置接口参数加解密过滤器")
    public static void setApiEncryptFilter(ApiEncryptFilter apiEncryptFilter) {
        put(API_FILTER, apiEncryptFilter);
    }
    @Note("获取接口参数加解密过滤器")
    public static ApiEncryptFilter getApiEncryptFilter() {
        return get(API_FILTER);
    }

    @Note("设置返回值信息")
    public static void responseErrorResult(Result info){
        responseErrorResult(OS.getResponse(),info);
    }
    @Note("设置返回值信息")
    public static void responseErrorResult(HttpServletResponse response , Result info){
        if (response == null){
            log.error("发送错误返回信息失败! respons 为空!");
            return;
        }
        try (ServletOutputStream outputStream = response.getOutputStream();){
            info.setMessage(MDC.get("requestId")+" "+info.getMessage());
            //返回响应
            response.setCharacterEncoding("UTF-8");
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            outputStream.write(LX.toJSONString(info).getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.error("发送错误返回信息失败!",e);
        }
    }
    @Note("获取当前线程的HttpServletRequest")
    public static HttpServletRequest getRequest(){
        return getRequestInfo().getRequest();
    }
    @Note("获取当前线程的HttpServletResponse")
    public static HttpServletResponse getResponse(){
        return getRequestInfo().getResponse();
    }

    @Note("获取请求头里的参数")
    public static String getHeader(String key) {
        return getRequest().getHeader(key);
    }

    private static final List<String> POST_METHOD = Arrays.asList("POST","PUT");

    @Note("获取Request的参数")
    public static Var getParamter() throws IOException {
        HttpServletRequest request = getRequest();
        if (POST_METHOD.contains(request.getMethod().toUpperCase())) {
            return LX.toMap(StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8")));
        }else{
            Var res = new Var();
            request.getParameterMap().forEach((k,v)->{
                res.put(k, v[0]);
            });
            return res;
        }
    }

    @Note("单位时间内指定key调用次数; key:限制标识, callFrequency:次数/时间 [1-9]\\d{0,5}/([M,d]|([1-9]\\d{0,3}[H,m,s]))")
    public static boolean allowAccessByCallFrequency(String key, String callFrequency) {
        return CacheUtils.allowAccessByCallFrequency(key,callFrequency);
    }
    @Note("单位时间内指定key调用次数,limit:次数 , time:秒")
    public static boolean allowAccessTo(String key , long limit, long time){
        return CacheUtils.allowAccessTo(key,limit,time);
    }
    @Note("清除已记录单位时间内的key")
    public static void allowAccessKeyClear(String key){
        CacheUtils.evict(key);
    }

    @Note("判断当前ip是否匹配白名单")
    public static boolean allowAccessByIp(String ipWhiteList){
        String ip = OS.getIp(getRequest());
        return !(LX.isEmpty(ip) || LX.isEmpty(ipWhiteList) || !ip.matches(ipWhiteList));
    }
    @Note("获取当前调用者IP")
    public static String getIp() {
        return getIp(getRequest());
    }

    @Note("获取Request的IP")
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-real-ip");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("x-forwarded-for");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if ("0:0:0:0:0:0:0:1".equals(ip)){
            return "127.0.0.1";
        }
        return ip;
    }




    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        OS.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setEnvironment(Environment environment) {
        OS.environment = environment;
    }

    public static Environment getEnvironment() {
        return environment;
    }

}

